/*
 * Class
 * UniformDistribution
 */

package pl.abstractvoid.distributions;

import java.util.Random;
import java.util.TreeMap;
import pl.abstractvoid.datamodel.parameters.ParameterSet;
import pl.abstractvoid.datamodel.parameters.SingleParameter;
import pl.abstractvoid.datamodel.parameters.exceptions.NoSuchParameterException;
import pl.abstractvoid.distributions.exceptions.ParameterValidationFailException;
import pl.abstractvoid.rconnector.RCallerInstance;
import pl.abstractvoid.rconnector.RInterpreter;
import rcaller.exception.ParseException;

/**
 * Represents continuous uniform probability distribution.
 * 
 * @author Wojciech Szałapski
 */
public class UniformDistribution extends AbstractDistribution {    
    
    /**
     * String representing the name of the variable which stores the left 
     * boundary of the interval in R.
     */
    private final static String leftBoundaryString = "leftBoundary";
    /**
     * String representing the name of the variable which stores the right 
     * boundary of the interval in R.
     */
    private final static String rightBoundaryString = "rightBoundary";
    /**
     * Names for output values used by R interpreter.
     */
    private final static String[] outputTableNames;
    /**
     * Names for output values used by user interface.
     */
    private final static String[] outputTableVisibleNames;
    /**
     * Buffer for the script executed when the output is computed.
     */
    private final static StringBuilder uniformSetUpOutputScript = new StringBuilder();
    /**
     * Buffer for the script executed when the cumulative distribution plot is
     * generated.
     */
    private final static StringBuilder uniformSetUpCumulativeDistributionPlotScript = new StringBuilder();
    /**
     * Buffer for the script executed when the probability density/mass function 
     * plot is generated.
     */
    private final static StringBuilder uniformSetUpProbabilityPlotScript = new StringBuilder();
    
    /*
     * Initialization of:
     *  - Names of output parameters.
     *  - Script for computing output parameters.
     *  - Scripts for generating plots.
     */
    static {
        final int numberOfParameters = 3;
        outputTableNames = new String[numberOfParameters];
        outputTableVisibleNames = new String[numberOfParameters];
        outputTableNames[0] = "expectedValue";
        outputTableNames[1] = "variance";
        outputTableNames[2] = "standardDeviation";
        
        uniformSetUpOutputScript.append(outputTableNames[0]).append(" = (").
                append(RInterpreter.inputRList).append("$").append(leftBoundaryString).append(" + ").
                append(RInterpreter.inputRList).append("$").append(rightBoundaryString).append(") / 2\n");
        uniformSetUpOutputScript.append(outputTableNames[1]).append(" = (").
                append(RInterpreter.inputRList).append("$").append(rightBoundaryString).append(" - ").
                append(RInterpreter.inputRList).append("$").append(leftBoundaryString).append(")^2 / 12\n");
        uniformSetUpOutputScript.append(outputTableNames[2]).append(" = ").
                append(outputTableNames[1]).append("^(1/2)\n");
        uniformSetUpOutputScript.append(RInterpreter.outputRList).append(" = list(");
        String prefix = "";
        for (String name : outputTableNames) {
            uniformSetUpOutputScript.append(prefix).append(name).append(" = ").append(name);
            prefix = ", ";
        }
        uniformSetUpOutputScript.append(")");
        
        final int plotPrecision = 1000;
        uniformSetUpCumulativeDistributionPlotScript.append("plotX = seq(").
                append(RInterpreter.inputRList).append("$").append(leftBoundaryString).
                append(", ").
                append(RInterpreter.inputRList).append("$").append(rightBoundaryString).
                append(", length.out = ").append(plotPrecision).append(")\n").
                append("normalPlot(plotX, punif(plotX, ").
                append(RInterpreter.inputRList).append("$").append(leftBoundaryString).append(", ").
                append(RInterpreter.inputRList).append("$").append(rightBoundaryString).
                append("), type = 'l')");
        
        uniformSetUpProbabilityPlotScript.append(uniformSetUpCumulativeDistributionPlotScript.
                toString().replaceAll("punif", "dunif"));
    }

    /**
     * Constructs the distribution using given input parameters.
     * 
     * @param leftBoundary The left boundary of the interval.
     * @param rightBoundary The right boundary of the interval.
     * @throws ParameterValidationFailException
     * @throws NoSuchParameterException 
     */
    public UniformDistribution(double leftBoundary, double rightBoundary)
            throws ParameterValidationFailException, NoSuchParameterException {
        super(uniformSetUpOutputScript, uniformSetUpCumulativeDistributionPlotScript, 
              uniformSetUpProbabilityPlotScript);
        distributionName = "uniformDistribution";
        rState = new RCallerInstance();
        SingleParameter leftBoundaryParam = new SingleParameter(leftBoundaryString);
        leftBoundaryParam.setParameterValue(leftBoundary);
        SingleParameter rightBoundaryParam = new SingleParameter(rightBoundaryString);
        rightBoundaryParam.setParameterValue(rightBoundary);
        TreeMap<String, SingleParameter> singleParameters = new TreeMap<>();
        singleParameters.put(leftBoundaryString, leftBoundaryParam);
        singleParameters.put(rightBoundaryString, rightBoundaryParam);
        ParameterSet parameterSet = new ParameterSet();
        parameterSet.setSingleParameters(singleParameters);
        updateInput(parameterSet);
    }

    @Override
    protected void validate(ParameterSet parameters) throws ParameterValidationFailException {
        TreeMap<String, SingleParameter> singleParameters = parameters.getSingleParameters();
        SingleParameter leftBoundaryParam = singleParameters.get(leftBoundaryString);
        SingleParameter rightBoundaryParam = singleParameters.get(rightBoundaryString);
        if (leftBoundaryParam.getParameterValue() >= rightBoundaryParam.getParameterValue()) {
            throw new ParameterValidationFailException("inversedIntervalBoundaries");
        }
    }
    
    @Override
    public ParameterSet loadParameters() throws NoSuchParameterException, ParseException {
        rState.runCustomCode("", RInterpreter.inputRList);
        SingleParameter leftBoundaryParam = new SingleParameter(leftBoundaryString);
        leftBoundaryParam.setParameterValue(getSingleParameterValue(leftBoundaryString));
        SingleParameter rightBoundaryParam = new SingleParameter(rightBoundaryString);
        rightBoundaryParam.setParameterValue(getSingleParameterValue(rightBoundaryString));
        TreeMap<String, SingleParameter> singleParameters = new TreeMap<>();
        singleParameters.put(leftBoundaryString, leftBoundaryParam);
        singleParameters.put(rightBoundaryString, rightBoundaryParam);
        ParameterSet result = new ParameterSet();
        result.setSingleParameters(singleParameters);
        return result;
    }

    @Override
    protected void initializeOutputTable() {
        outputTable = new SingleParameter[outputTableNames.length];
        for (int i = 0; i < outputTableNames.length; ++i) {
            outputTable[i] = new SingleParameter(outputTableNames[i]);
        }
    }
    
    /**
     * Returns a new instance of a uniform distribution with random interval 
     * which is a sub-interval of [-50; 50.001).
     * 
     * @return New instance of uniform distribution with random parameters.
     */
    public static UniformDistribution getSampleDistributionData() {
        Random generator = new Random();
        double leftBoundary = generator.nextDouble() * 50;
        double rightBoundary = generator.nextDouble() * 50;
        int swap = generator.nextInt(4);
        switch (swap) {
            case 0:
                leftBoundary = -leftBoundary;
                break;
            case 1:
                rightBoundary = -rightBoundary;
                break;
            case 2:
                leftBoundary = -leftBoundary;
                rightBoundary = -rightBoundary;
                break;
        }
        if (leftBoundary == rightBoundary) {
            rightBoundary += 0.001;
        } else if (leftBoundary > rightBoundary) {
            double temp = leftBoundary;
            leftBoundary = rightBoundary;
            rightBoundary = temp;
        }
        try {
            return new UniformDistribution(leftBoundary, rightBoundary);
        } catch (ParameterValidationFailException | NoSuchParameterException ex) {
            return null;
        }
    }
}

